/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: manifest_parser.cpp
 *
 * Purpose: manifest parser implementation 
 *
 * Developer:
 *   wen.gu , 2024-01-13
 *
 * TODO:
 *
 ***************************************************************************/
#include "xgit/manifest_parser.h"

#include "yaml-cpp/yaml.h"

#define GET_OBJ_ITEM(obj, name, out_type, out_value, error_info) do {\
    if (!obj[name]) {printf(error_info); return false; } \
    out_value = obj[name].as<out_type>(); \
}while(0)

#define GET_OBJ_OPT_ITEM(obj, name, out_type, out_value) do {\
    if (obj[name]) {out_value = obj[name].as<out_type>(); } \
}while(0)

#define GET_OBJ_OPT_ITEM2(obj, name, out_type, out_value, def_val) do {\
    if (!obj[name]) { out_value = def_val; } \
    out_value = obj[name].as<out_type>(); \
}while(0)


namespace xgit {


static bool ManifestRmoteParse(YAML::Node& manifest, std::map<std::string, Remote>& remotes) {
    if (manifest["remote"]) {
        for (const auto& remote_obj: manifest["remote"]) {
            Remote remote_info;
            GET_OBJ_ITEM(remote_obj, "name", std::string, remote_info.name, "syntax error: a remote object must have \'name\' element\n");
            GET_OBJ_ITEM(remote_obj, "fetech", std::string, remote_info.fetch_url, "syntax error: a remote object must have \'fetech\' element\n");
            GET_OBJ_OPT_ITEM(remote_obj, "push", std::string, remote_info.push_url);
            GET_OBJ_OPT_ITEM(remote_obj, "review", std::string, remote_info.review_url);
            remotes[remote_info.name] = remote_info;
        }
        return true;
    }

    return false;
}

static bool ManifestDefaultParse(YAML::Node& manifest, DefaultInfo& default_info) {
    if (manifest["default"]) {
        const auto& default_obj = manifest["default"];
        GET_OBJ_ITEM(default_obj, "remote", std::string, default_info.remote, "syntax error: a default object must have \'remote\' element\n");
        GET_OBJ_ITEM(default_obj, "revision", std::string, default_info.revision, "syntax error: a default object must have \'revision\' element\n");
        GET_OBJ_ITEM(default_obj, "sync_c", bool, default_info.sync_c, "syntax error: a default object must have \'sync_c\' element\n");
        GET_OBJ_ITEM(default_obj, "sync_s", bool, default_info.sync_s, "syntax error: a default object must have \'sync_s\' element\n");

        return true;
    }

    return false;
}

static bool ManifestPrjCommandParse(const YAML::Node& prj_obj, std::vector<PrjCommand>& cmds) {
    if (prj_obj["commands"]) {
        for (const auto& cmd_obj: prj_obj["commands"]) {
            PrjCommand cmd_info;
            GET_OBJ_ITEM(cmd_obj, "cmd", std::string, cmd_info.cmd, "syntax error: a command object must have \'cmd\' element\n");
            GET_OBJ_ITEM(cmd_obj, "src", std::string, cmd_info.src, "syntax error: a command object must have \'src\' element\n");
            GET_OBJ_ITEM(cmd_obj, "dst", std::string, cmd_info.dst, "syntax error: a command object must have \'dst\' element\n");
        }
    }

    return true;    
}

static bool ManifestProjectParse(YAML::Node& manifest, std::map<std::string, Project>& projects, DefaultInfo& def_info) {
    if (manifest["project"]) {
        for (const auto& project_obj: manifest["project"]) {
            Project project_info;
            GET_OBJ_ITEM(project_obj, "name", std::string, project_info.name, "syntax error: a project object must have \'name\' element\n");
            GET_OBJ_OPT_ITEM2(project_obj, "path", std::string, project_info.path, project_info.name);
            GET_OBJ_OPT_ITEM2(project_obj, "remote", std::string, project_info.remote, def_info.remote);
            GET_OBJ_OPT_ITEM2(project_obj, "revision", std::string, project_info.revision, def_info.revision);
            GET_OBJ_OPT_ITEM2(project_obj, "sync_c", bool, project_info.sync_c, def_info.sync_c);
            GET_OBJ_OPT_ITEM2(project_obj, "sync_s", bool, project_info.sync_c, def_info.sync_s);
            
            if (!ManifestPrjCommandParse(project_obj, project_info.commands)) {
                return false;
            }

            projects[project_info.name] = project_info;
        }

        return true;
    }

    return false;
}



ManifestParser::ManifestParser() {
    //todo something
}

ManifestParser::~ManifestParser() {
    //todo something
}


ManifestSp ManifestParser::operator()(const std::string& url) {
    YAML::Node manifest_file;
    // 当文件不存在或yaml格式出错时，抛异常
    try {
        manifest_file = YAML::LoadFile(url);
    } catch (...) {
        printf("error loading file, yaml file error or not exist.\n");
        return nullptr;
    }
    
    ManifestSp manifest_ptr = std::make_shared<Manifest>();

    if (!ManifestRmoteParse(manifest_file, manifest_ptr->remote)) {
        printf("parse remote information faield\n");
        return nullptr;
    }

    if (!ManifestDefaultParse(manifest_file, manifest_ptr->default_info)) {
        printf("parse default information faield\n");
        return nullptr;
    }    
    
    if (!ManifestProjectParse(manifest_file, manifest_ptr->project, manifest_ptr->default_info)) {
        printf("parse project information faield\n");
        return nullptr;
    }

    return manifest_ptr;
}










} //namespace xgit
